/*
    BibleNotebook - A bible reader software
    Copyright (C) 2014  Williams Fernando Lopez

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
 */

package com.simplebible.dictionary;

import com.simplebible.dictionary.objects.SimpleIndex;
import com.simplebible.dictionary.objects.DictDefinition;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import wl.util.Utils;
import com.simplebible.util.VirtualFolder;
import com.simplebible.objects.Preferences;

/**
 *
 * @author Williams Lopez
 */
public class DictionaryFile implements IDictionary{

    private String path;
    private String dictionaryName;
    private String dictionaryDescription;
    private String dictionaryUrl;
    private String dictionaryCopyright;
    private String dictionaryId;
    private String idioma;

    private VirtualFolder vfDictionary;

    private String sep1RegEx = "[{][$][$][$][}]";
    private String sep2RegEx = "[{][$][$][}]";
    private String sep3RegEx = "[{][$][}]";

    private String sep1 = "{$$$}";
    private String sep2 = "{$$}";
    private String sep3 = "{$}";


    //PRIVATE FILES
    private String FILE_ENTRY_COUNT = "_df_count";
    private String FILE_DICT_INFO  = "_df_dict_info";

    //All words
    private List<String> allWords = null;
    private SimpleIndex simpleIndex = null;



    public DictionaryFile(String dictid) throws IOException{
        vfDictionary = new VirtualFolder(dictid, false);
        if(!vfDictionary.exists(FILE_ENTRY_COUNT)){
            vfDictionary.write(FILE_ENTRY_COUNT, "0".getBytes());
        }

        path = dictid;
        File f = new File(dictid);
        dictionaryId = f.getName();

        loadDictionaryInfo();
    }


    public  void close() throws IOException{
        if(vfDictionary!=null){
            vfDictionary.close();
        }
    }    

    public DictDefinition get(String word) throws Exception{
        String wordKey = getWordKey(word);

        if(!getAllWords().contains(wordKey)){
            return null;
        }

        String fileName = getFileNameFromWordKey(wordKey);

        if(!vfDictionary.exists(fileName)){
            return null;
        }

        WordPosition wp = readWordPosition(wordKey, fileName);

        if(wp==null){
            return null;
        }
        
        byte[] datos =vfDictionary.read(fileName, wp.offset, wp.length);
        DictDefinition def = unserialize(datos);
        def.setWord(word);
        def.setWordKey(wordKey);

        return def;
    }

    public void put(DictDefinition def) throws Exception{

        DictDefinition exists = get(def.getWord());

        if(exists !=null){
            throw new Exception("La definición ya existe ["+def.getWord()+"]");
        }

        String word = def.getWord();
        String wordKey = getWordKey(word);
        String fileName = getFileNameFromWordKey(wordKey);
        def.setWordKey(wordKey);
        byte[] serialized = serialize(def);

        int sizeFile = 0;
        try {
            sizeFile = vfDictionary.size(fileName);
        } catch (Exception e) {
        }

        vfDictionary.append(fileName+".idx",(wordKey+","+sizeFile+","+(serialized.length)+"\n").getBytes());
        vfDictionary.append(fileName,serialized);

        setEntryCount(getEntryCount()+1);

        getAllWords().add(wordKey);

    }

    public boolean remove(String word) throws Exception {
        String wordKey  = getWordKey(word);

        if(!allWords.contains(wordKey)){
            return false;
        }

        String fileName = getFileNameFromWordKey(wordKey);
        List<WordPosition> wordsInFile = readWordIndexFile(fileName);
        List<DictDefinition> defs = new ArrayList<DictDefinition>();
        WordPosition wordPos = null;

        for(WordPosition wp : wordsInFile){
            if(wp.wordKey.equals(wordKey)){
                wordPos = wp;
            }
            defs.add(get(wp.wordKey));
        }

        if(wordPos == null){
            return false;
        }

        vfDictionary.truncate(fileName);
        vfDictionary.truncate(fileName+".idx");

        for(DictDefinition d : defs){
            if(d==null) continue;
            put(d);
        }

        getAllWords().remove(wordKey);

        return true;

    }

    public List<String> listSimilarWords(String word) throws Exception{
        String wordKey = getWordKey(word);
        String fileName = getFileNameFromWordKey(wordKey);
        List<WordPosition> wordsInFile = readWordIndexFile(fileName);
        List<String> found = new ArrayList<String>();

        for(WordPosition wp: wordsInFile){
            if(Utils.areSimilar(word.toUpperCase(),wp.wordKey)){
                found.add(wp.wordKey);
            }
        }

        return found;
    }


     public String getDictionaryCopyright() {
        return dictionaryCopyright;
    }

    public String getDictionaryDescription() {
        return dictionaryDescription;
    }

    public String getDictionaryName() {
        return dictionaryName;
    }

    public String getDictionaryUrl() {
        return dictionaryUrl;
    }

    public void setDictionaryInfo(String name,String url, String copiryght,String description,String idioma) throws IOException{
        this.dictionaryName = name;
        this.dictionaryUrl = url;
        this.dictionaryCopyright = copiryght;
        this.dictionaryDescription = description;
        this.idioma = idioma;
        saveDictionaryInfo();
    }

    public String getDictionaryId() {
        return dictionaryId;
    }

    public int size() {
        try {
            return getEntryCount();
        } catch (Exception e) {
        }
        return 0;
    }





    //PRIVATE METHODS

    private WordPosition readWordPosition(String wordKey,String fileName) throws IOException{

        String wordKeyComma = wordKey+",";
        String str= new String(vfDictionary.read(fileName+".idx"),"UTF-8");
        StringBuilder sb = new StringBuilder();

        for(int i=0;i<str.length();i++){
            char c = str.charAt(i);
            if(c=='\n'){
                String line = sb.toString();
                if(line.startsWith(wordKeyComma)){
                    return parseWordPosition(line,fileName);
                }
                sb.delete(0, sb.length());
            }else{
                sb.append(c);
            }
        }

        return null;
    }

    private List<WordPosition> readWordIndexFile(String fileName) throws IOException{
        List<WordPosition> wp = new ArrayList<WordPosition>();
        String str= new String(vfDictionary.read(fileName+".idx"),"UTF-8");
        String[] data = str.split("\\n");
        for(String line: data){
            if(line.trim().equals("")) continue;
            wp.add(parseWordPosition(line, fileName));
        }
        return wp;
    }

    private WordPosition parseWordPosition(String str, String fileName){
        WordPosition wp = new WordPosition();
        String[] data = str.split(",");
        wp.fileName = fileName;
        wp.wordKey = data[0];
        wp.offset = Integer.parseInt(data[1]);
        wp.length = Integer.parseInt(data[2]);
        return wp;
    }

    private String getFileNameFromWordKey(String wordKey) throws Exception{
        String fileName;

        if(wordKey.length()>2){
            fileName = wordKey.substring(0, 3);
        }else if(wordKey.length()>1){
            fileName = wordKey.substring(0, 2)+wordKey.charAt(1);
        }else if(wordKey.length()>0){
            fileName = wordKey + wordKey + wordKey;
        }else{
            fileName = "DEFAULTWORD";
        }

        fileName = fileName.toUpperCase();
        String key = "";

        for(int i=0;i<fileName.length();i++){
            char c = fileName.charAt(i);
            if(Character.isLetter(c)){
                switch(c){
                    case 'á': case 'ä': case 'Á': case 'Ä': c = 'A'; break;
                    case 'é': case 'ë': case 'É': case 'Ë': c = 'E'; break;
                    case 'í': case 'ï': case 'Í': case 'Ï': c = 'I'; break;
                    case 'ó': case 'ö': case 'Ó': case 'Ö': c = 'O'; break;
                    case 'ú': case 'ü': case 'Ú': case 'Ü': c = 'U'; break;
                    case 'ñ': case 'Ñ':                     c = 'N'; break;
                }

                key+=c;
            }
        }

        if(key.isEmpty()){
            key = "DEFAULTWORD";
        }

        return key;
    }

    private byte[] serialize(DictDefinition def) throws IOException{

        ByteArrayOutputStream buff = new ByteArrayOutputStream();

        if (def.getDefinitions() != null) {
            for (int i = 0; i < def.getDefinitions().length; i++) {
                buff.write(def.getDefinitions()[i].trim().getBytes());
                buff.write(sep3.getBytes());
            }
        }else{
            buff.write("-".getBytes());
        }
        buff.write(sep2.getBytes());
        if (def.getReferences() != null) {
            for (int i = 0; i < def.getReferences().length; i++) {
                buff.write(def.getReferences()[i].getBytes());
                buff.write(sep3.getBytes());
            }
        }else{
            buff.write("-".getBytes());
        }

        return buff.toByteArray();
    }

    private DictDefinition unserialize(byte[] data) throws UnsupportedEncodingException{
        String str = new String(data,"UTF-8");
        String[] info = str.split(sep2RegEx);
        DictDefinition def = new DictDefinition();

        String[] defArray = info[0].split(sep3RegEx);
        if(defArray.length>0 && !defArray[0].trim().equals("-")){
            def.setDefinitions(defArray);
        }

        String[] refArray = info[1].split(sep3RegEx);
        if(refArray.length>0 && !refArray[0].trim().equals("-")){
            def.setReferences(refArray);
        }
        return def;
    }

    private String getWordKey(String str) throws Exception{
        str = str.trim();
        StringBuilder key = new StringBuilder();
        for(int i=0;i<str.length();i++){
            char c = str.charAt(i);
            if(Character.isLetterOrDigit(c)){
                switch(c){
                    case 'á': c = 'Á'; break;
                    case 'é': c = 'É'; break;
                    case 'í': c = 'Í'; break;
                    case 'ó': c = 'Ó'; break;
                    case 'ú': c = 'Ú'; break;
                    case 'ä': c = 'Ä'; break;
                    case 'ë': c = 'Ë'; break;
                    case 'ï': c = 'Ï'; break;
                    case 'ö': c = 'Ö'; break;
                    case 'ü': c = 'Ü'; break;
                }

                key.append(c);
            }else{
                key.append(c);
            }
        }

        if(str.isEmpty()){
            throw new Exception("Can not get a word key for word:"+str);
        }

        return key.toString().toUpperCase();
    }

    private int getEntryCount() throws IOException{
        String text = new String(vfDictionary.read(FILE_ENTRY_COUNT));
        return Integer.parseInt(text);
    }

    private void setEntryCount(int count) throws IOException{
        vfDictionary.write(FILE_ENTRY_COUNT, Integer.toString(count).getBytes());
    }

    private void saveDictionaryInfo() throws IOException{

        vfDictionary.truncate(FILE_DICT_INFO);

        vfDictionary.append(FILE_DICT_INFO, dictionaryName.getBytes());
        vfDictionary.append(FILE_DICT_INFO, sep1.getBytes());

        //URL
        if(dictionaryUrl!=null){
            vfDictionary.append(FILE_DICT_INFO, dictionaryUrl.getBytes());
        }else{
            vfDictionary.append(FILE_DICT_INFO, "--".getBytes());
        }
        vfDictionary.append(FILE_DICT_INFO, sep1.getBytes());

        //COPYRIGHT
        if(dictionaryCopyright!=null){
            vfDictionary.append(FILE_DICT_INFO, dictionaryCopyright.getBytes());
        }else{
            vfDictionary.append(FILE_DICT_INFO, "--".getBytes());
        }
        vfDictionary.append(FILE_DICT_INFO, sep1.getBytes());

        //DESCRIPCION
        if(dictionaryDescription!=null){
            vfDictionary.append(FILE_DICT_INFO, dictionaryDescription.getBytes());
        }else{
            vfDictionary.append(FILE_DICT_INFO, "--".getBytes());
        }
        vfDictionary.append(FILE_DICT_INFO, sep1.getBytes());

        //IDIOMA
        if(idioma==null){
            idioma = Preferences.IDIOMA_ES;
        }
        vfDictionary.append(FILE_DICT_INFO, idioma.getBytes());

        vfDictionary.append(FILE_DICT_INFO, sep1.getBytes());
    }

    private final void loadDictionaryInfo() throws IOException{

        if(!vfDictionary.exists(FILE_DICT_INFO)){
            return;
        }

        String text = new String(vfDictionary.read(FILE_DICT_INFO),"UTF-8");
        String data[] = text.split(sep1RegEx);
        dictionaryName          = data[0].trim();
        dictionaryUrl           = data[1].trim();
        dictionaryCopyright     = data[2].trim();
        dictionaryDescription   = data[3].trim();
        if(data.length>4){
            idioma                  = data[4].trim();
        }else{
            idioma                  = Preferences.IDIOMA_ES;
        }
    }

    public List<String> getAllWords() throws IOException{

        if(allWords!=null){
            return allWords;
        }

        String[] files = vfDictionary.getFiles();
        allWords = new ArrayList<String>();
        for(String f : files){
            if(f.endsWith(".idx")){
                List<WordPosition> words = readWordIndexFile(f.substring(0,f.lastIndexOf('.')));
                if(words!=null){
                    for(WordPosition w:words){
                        allWords.add(w.wordKey);
                    }
                }
            }
        }
        return allWords;
    }

    @Override
    public String toString() {
        return "[DIC-"+getDictionaryId()+"] "+getDictionaryName();
    }

    public boolean removeOfSystem() throws Exception {
        close();
        File file1 = new File(path);
        File file2 = new File(path + ".files");
        return !file1.delete() || !file2.delete();
    }

    public String getIdioma() {
        return idioma;
    }

    public SimpleIndex getIndex() throws Exception {
        if(simpleIndex==null){
            simpleIndex=DictionaryUtils.createSimpleIndex(getAllWords());
        }
        return simpleIndex;
    }

    




    


    class WordPosition{
        String fileName;
        String wordKey;
        int offset;
        int length;
    }

    

}
